package com.ken.common.redis.lock;

import com.ken.common.core.utils.ApplicationContextUtils;
import com.ken.common.redis.transaction.TransactionMode;
import com.ken.common.redis.transaction.TransactionUtils;
import com.ken.common.redis.utils.RLockThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * redis分布式锁
 */
@Slf4j
public class LockUtils {

    private static RedissonClient redissonClient;

    static {
        //获得RedissonClient对象
        redissonClient = ApplicationContextUtils.getBean(RedissonClient.class);
    }

    /**
     * redis上锁 - 没有获得锁该方法会立刻返回null
     * @return
     */
    public static boolean tryLock(String key){
        log.debug("[Cluster-Lock] - 尝试分布式锁 - {}", key);
        RLock lock = redissonClient.getLock(key);
        //尝试获得分布式锁
        if (lock != null && lock.tryLock()) {
            log.debug("[Cluster-Lock-Succ] - 获取分布式锁成功 - {}", key);
            RLockThreadLocal.setRLock(lock);
            return true;
        }
        log.warn("[Cluster-Lock-Fail] - 获取分布式锁失败 - {}", key);
        return false;
    }

    /**
     * redis上锁 - 没有获得锁会阻塞直到获得锁为止
     * @param key
     * @return
     */
    public static boolean lockSync(String key) {
        log.debug("[Cluster-Lock] - 阻塞式获取分布式锁 - {} - {}", key, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
        RLock lock = redissonClient.getLock(key);
        if (lock != null) {
            while (!lock.tryLock()) ;
            log.debug("[Cluster-Lock-Succ] - 阻塞式获取分布式锁成功 - {} - {}", key, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
            //将上锁的对象放入ThreadLock中
            RLockThreadLocal.setRLock(lock);
            return true;
        }
        log.warn("[Cluster-Lock-Fail] - 阻塞式获取分布式锁失败 - {} - {}", key, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
        throw new RuntimeException("获得分布式锁异常！");
    }

    /**
     * redis上锁 - 超过时间没获得锁就会返回null
     * @param key
     * @param time
     * @param unit
     * @return
     */
    public static boolean lockTime(String key, Long time, TimeUnit unit){
        log.debug("[Cluster-Lock] - 超时式获取分布式锁 - {} - {}", key, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
        RLock lock = redissonClient.getLock(key);
        try {
            if (lock.tryLock(time, unit)){
                log.debug("[Cluster-Lock-Succ] - 超时式获取分布式锁成功 - {} - {}", key, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
                RLockThreadLocal.setRLock(lock);
                return true;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.warn("[Cluster-Lock-Fail] - 超时式获取分布式锁失败 - {} - {}", key, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
        return false;
    }

    /**
     * 解锁方法 - 用于AOP解锁
     * @return
     */
    public static boolean unlock(){
        log.debug("[Cluster-unLock] - 解除分布式锁");
        //将上锁的对象放入ThreadLock中
        RLock rLock = RLockThreadLocal.getRLock();
        //清空ThreadLocal
        RLockThreadLocal.clear();
        if (rLock != null && rLock.isLocked()) {
            //解除分布式锁
            rLock.unlock();
            log.debug("[Cluster-unLock-Succ] - 解除分布式锁成功 - {}", rLock.getName());
            return true;
        }
        log.debug("[Cluster-unLock-Succ] - 解除分布式锁失败 - {}", rLock);
        return false;
    }


    /**
     * ---------------------事务模式操作相关----------------------
     */
    /**
     * 添加事务锁
     * @param key
     * @return
     */
    public static LockTransaction lockTransaction(String key){
        //添加分布式锁
        LockUtils.lockSync(key);
        return new LockTransaction();
    }


    /**
     * 事务锁
     */
    public static class LockTransaction {

        /**
         * 设置事务模式 - 模式
         * @return
         */
        public LockTransaction mode(TransactionMode mode){
            //开启事务
            TransactionUtils.startTransaction(mode);
            return this;
        }

        /**
         * 执行相应方法
         */
        public <T> T exec(Supplier<T> supplier){
            try {
                T result = supplier.get();
                //提交事务
                TransactionUtils.commit();
                //返回结果
                return result;
            } catch (Exception e) {
                //回滚事务
                TransactionUtils.rollback();
                throw e;
            } finally {
                //解除分布式锁
                LockUtils.unlock();
            }
        }
    }
}
